use std::{iter::Peekable, str::Chars};

pub struct Reader<'s> {
	line: usize,
	column: usize,
	offset: usize,
	content: &'s str,
	cursor: Peekable<Chars<'s>>,
}
impl<'s> Iterator for Reader<'s> {
	type Item = char;
	fn next(&mut self) -> Option<char> {
		match self.cursor.next() {
			Some(c) => {
				if c == '\n' {
					self.line += 1;
					self.column = 1;
				} else {
					self.column += 1;
				}
				self.offset += c.len_utf8();
				Some(c)
			}
			_ => None,
		}
	}
}
impl<'s> Reader<'s> {
	#[inline]
	pub fn new(content: &'s str) -> Self {
		Reader {
			line: 1,
			column: 1,
			offset: 0,
			content,
			cursor: content.chars().peekable(),
		}
	}
	#[inline]
	pub fn peek(&mut self) -> Option<char> {
		self.cursor.peek().copied()
	}
	#[inline]
	pub fn peek_many(&self) -> Chars<'s> {
		self.content[self.offset..].chars()
	}
	#[inline]
	pub fn pos(&self) -> (usize, usize) {
		(self.line, self.column)
	}
	pub fn capture(&mut self, block: impl Fn(&mut Reader<'s>) -> bool) -> Option<&'s str> {
		let start = self.offset;
		block(self).then_some("")?;
		let content = self.content[start..self.offset].trim();
		(!content.is_empty()).then_some(content)
	}
	pub fn eat_if(&mut self, check: impl Fn(char) -> bool) -> bool {
		match self.peek() {
			Some(c) if check(c) => {
				self.next();
				true
			}
			_ => false,
		}
	}
	pub fn eat_while(&mut self, check: impl Fn(char) -> bool) -> bool {
		let mut consumed = false;
		while self.eat_if(&check) {
			consumed = true;
		}
		consumed
	}
	#[inline]
	pub fn eat_all(&mut self) -> bool {
		self.eat_while(|_| true)
	}
	#[inline]
	pub fn eat_any(&mut self, targets: &str) -> bool {
		self.eat_while(|c| targets.contains(c))
	}
	#[inline]
	pub fn eat_until_any(&mut self, targets: &str) -> bool {
		self.eat_while(|c| !targets.contains(c))
	}
	#[inline]
	pub fn eat_char(&mut self, target: char) -> bool {
		self.eat_if(|c| c == target)
	}
	#[inline]
	pub fn eat_until_char(&mut self, target: char) -> bool {
		self.eat_while(|c| c != target)
	}
}
